﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace System.Threading.Actors.Utilities
{
    internal static class MethodCallUtilities
    {
        // This logic is used to call event handler.  Returns void (well, a Task) because we don't 
        // really care about the return value.
        public static async Task CallMethodOnStateAsync(IActorState state, string methodName, bool nameNeedsLookup, params object[] args)
        {
            try
            {
                await CallMethodOnStateAsyncThrowing(state, methodName, nameNeedsLookup, args);
            }
            catch (Exception)
            {
                // We already logged the exception.  Nothing to do.
            }
        }

        // This logic is used to call async task result continuation methods.  Returns an Object so we can pass back
        // the result (or throw an exception) to the code that started this continuation.  See IActorPromise's ContinueWith.
        internal static async Task<Object> CallMethodOnStateAsyncThrowing(IActorState state, string methodName, bool nameNeedsLookup, params object[] args)
        {
            Task waiterTask = null;
            Object returnValue = null;
            Exception exception = null;
            try
            {
                await state.AsyncStateLock.WaitAsync();

                if (nameNeedsLookup)
                {
                    object temp;
                    if (state.TryGet(methodName, out temp))
                    {
                        methodName = (string)temp;
                    }
                    else return null;  // Silently returns if the given name cannot be found as a key
                }
                try
                {
                    returnValue = state.CallMethod(methodName, args);
                    waiterTask = state.Flush();
                }
                catch (Exception e)
                {
                    var baseException = (e is AggregateException) ? ((AggregateException)e).GetBaseException() : e;
                    exception = baseException;
                    state.ClearChangesForCurrentOperation();
                    // Log an ETW event so that we can know that the continuation failed
                    ActorETWEventSource.Log.ActorMethodError(state.GetId(), state.PartitionName, methodName, args,
                        baseException.GetType().Name, baseException.Message, baseException.StackTrace);
                    throw;
                }
            }
            finally { state.AsyncStateLock.Release(); }

            if (waiterTask != null) // Wouldn't happen if method call threw exception
            {
                try
                {
                    await waiterTask;
                }
                catch (AggregateException ae)
                {
                    var baseException = ae.GetBaseException();
                    exception = baseException;
                    ActorETWEventSource.Log.ActorMethodError(state.GetId(), state.PartitionName, methodName, args,
                        baseException.GetType().Name, baseException.Message, baseException.StackTrace);
                    throw;
                }
            }

            // Only record successful method call if both the call itself and any necessary replication went well.
            ActorETWEventSource.Log.ActorMethodCompleted(state.GetId(), state.PartitionName, methodName, args, null);
            return returnValue;
        }
    }
}
